#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include <iostream>
#include <limits>

#include <cuda_runtime.h>
#include <helper_cuda.h>
#include <helper_functions.h>

#include <cv.h>
#include <highgui.h>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <log4cxx/logger.h>
#include <log4cxx/xml/domconfigurator.h>
#include <base/logging.h>
#include <base/types.cuh>
#include <base/methods.cuh>
#include <gpu/cu_methods.cuh>

#include <gpu/fast_match_helper.cuh>
#include <gpu/fast_match.cuh>

#include <gpu/ciratefi_helper.cuh>
#include <gpu/ciratefi.cuh>

#include <base/helper.h>
#include <debug/simple_test.cuh>
#include <main.cuh>

using namespace std;
using namespace cv;
using namespace recognition;

// Main Function
int main(int argc, const char * argv[]) {
	// Configuring logging
	string configFileStr(CONFIG_DIRECTORY);
	configFileStr.push_back(FILE_PATH_SEPARATOR);
	configFileStr.append(CONFIG_FILE_XML);
	log4cxx::xml::DOMConfigurator::configure(configFileStr.c_str());

	// Get default logger
	log4cxx::LoggerPtr logger(log4cxx::Logger::getRootLogger());
	LOG_INFO(logger, "Entering application.");
	LOG_INFO(logger, "Reading command line arguments...");

	if (!argv[1] || !argv[2]) {
		LOG_ERROR(logger, "Both input files must be specified!");
		exit(EXIT_FAILURE);
	}

	string images_dir_str(RESOURCES_DIRECTORY);
	images_dir_str.push_back(FILE_PATH_SEPARATOR);

	// Input camera and pattern images' file names
	string camera_file_str(images_dir_str + argv[1]);
	string pattern_file_str(images_dir_str + argv[2]);

	unsigned char *camera_elements_uc;		// camera image bytes
	unsigned char *pattern_elements_uc;		// pattern image bytes

	// Read the data and convert it into gray scale
//	camera_elements_uc = BmpRgbToGrayscale(ReadBmpRgb(cameraFileStr.c_str(), M, N), M, N);
//	pattern_elements_uc = BmpRgbToGrayscale(ReadBmpRgb(patternFileStr.c_str(), m, n), m, n);

	cv::Mat cam = cv::imread(camera_file_str.c_str(), CV_LOAD_IMAGE_GRAYSCALE);
	cv::Mat pat = cv::imread(pattern_file_str.c_str(), CV_LOAD_IMAGE_GRAYSCALE);
	camera_elements_uc = cam.ptr(0);
	pattern_elements_uc = pat.ptr(0);
	//%% image dimensions
	int h1, w1, h2, w2;
	h2 = cam.rows; w2 = cam.cols;
	h1 = pat.rows; w1 = pat.cols;

	LOG4CXX_INFO(logger, kInputArgumentsTableBorders);
	LOG_INFO(logger, "|%-15s|%-40s|%-10s|%-10s|", "Image", "File", "Width", "Height");
	LOG4CXX_INFO(logger, kInputArgumentsTableBorders);
	LOG_INFO(logger, "|%-15s|%-40s|%-10d|%-10d|", "camera", camera_file_str.c_str(), w2, h2);
	LOG_INFO(logger, "|%-15s|%-40s|%-10d|%-10d|", "pattern", pattern_file_str.c_str(), w1, h1);
	LOG4CXX_INFO(logger, kInputArgumentsTableBorders);
//	LOG_INFO(logger, "Target (x, y)  =  (%d, %d)", -1, -1);	// TODO: Opt is unspecified

	// Not cropped images
	double *_image_elements, *_template_elements;
	_image_elements = UcharToNormalizedDouble(camera_elements_uc, h2, w2);
	_template_elements = UcharToNormalizedDouble(pattern_elements_uc, h1, w1);

	// Cropped images with odd dimensions
	double *image_elements, *template_elements;

	image_elements = MakeOdd(_image_elements, h2, w2);
	template_elements = MakeOdd(_template_elements, h1, w1);

	free(_image_elements);
	free(_template_elements);

	// Input images
	cv::Mat image(h2, w2, CV_64FC1, image_elements);
	cv::Mat pattern(h1, w1, CV_64FC1, template_elements);

//	DEBUG_Recolor(pattern);
//	DEBUG_Recolor(image);

	// Read device properties
	int cuda_device = 0;
	checkCudaErrors(cudaSetDevice(cuda_device));
	cudaDeviceProp deviceProp;
	checkCudaErrors(cudaGetDeviceProperties(&deviceProp, cuda_device));
	int max_shared_memory = deviceProp.sharedMemPerBlock;

///////////////////////////////// Ciratefi /////////////////////////////////
	Mat I1_ciratefi, I2_ciratefi;
	do {
		I1_ciratefi = pattern.clone();
		I2_ciratefi = image.clone();

		// Smooth
		double blur_sigma = 4;
		double blur_size = 2 * blur_sigma - 1;
		GaussianBlur(I1_ciratefi, I1_ciratefi, Size(blur_size, blur_size), blur_sigma, blur_sigma, BORDER_REFLECT);
		GaussianBlur(I2_ciratefi, I2_ciratefi, Size(blur_size, blur_size), blur_sigma, blur_sigma, BORDER_REFLECT);
	} while (0);

	BCThresholds bc_thresholds(0.1, 1);
	CiratefiThresholds c_thresholds(0.8, 0.8, 0.5);

	CiratefiBoundaries c_bounds;
	c_bounds.s = BoundsWithStep<double>(0.1, 0.4, 1.3);
	c_bounds.r = BoundsWithStep<int>(2, 0, min(I1_ciratefi.cols / 2, I1_ciratefi.rows / 2) - c_bounds.r.step);
	c_bounds.phi = BoundsWithStep<double>(2 * M_PI / 36, 0, 2 * M_PI);
	c_bounds.angle_incr = 2;
	c_bounds.line_incr = 2;

	recognition::gpu::Ciratefi ciratefi = recognition::gpu::Ciratefi::Builder()
		.I1(I1_ciratefi)
		.I2(I2_ciratefi)
		.BC_thresholds(bc_thresholds)
		.thresholds(c_thresholds)
		.bounds(c_bounds)
		.maximum_shared_memory(max_shared_memory)
		.build();

	// Uncomment to run ciratefi and display the result
//	DEBUG_RunCiratefi(ciratefi);
//	exit(0);

///////////////////////////////// FAsT-Match /////////////////////////////////
	Mat I1_fast_match, I2_fast_match;
	do {
		I1_fast_match = pattern.clone();
		I2_fast_match = image.clone();

//		double blur_sigma = 2;
//		double blur_size = 4 * blur_sigma - 1;

		// Smooth
		double blur_sigma = 4;
		double blur_size = 2 * blur_sigma - 1;
		GaussianBlur(I1_fast_match, I1_fast_match, Size(blur_size, blur_size), blur_sigma, blur_sigma, BORDER_REFLECT);
		GaussianBlur(I2_fast_match, I2_fast_match, Size(blur_size, blur_size), blur_sigma, blur_sigma, BORDER_REFLECT);
	} while (0);

	// Set default values for optional variables
	double epsilon, delta, use_photometric, min_scale, max_scale;
	epsilon = 0.15;
	delta = 0.25;
	use_photometric = 1;
	min_scale = 0.5;
	max_scale = 2;

	recognition::gpu::FAsT_Match fast_match = recognition::gpu::FAsT_Match::Builder()
		.I1(I1_fast_match)
		.I2(I2_fast_match)
		.ScaleRange(min_scale, max_scale)
		.Delta(delta)
		.Epsilon(epsilon)
		.PhotometricInvariance(use_photometric)
		.build();

	// Uncomment to run FAsT-Match and display the result
	DEBUG_RunFastMatch(fast_match);
//	exit(0);

	LOG4CXX_INFO(logger, "Exiting application...");
    return 0;
}
